﻿using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Xml;
using Newtonsoft.Json;
using System.IO;
using System.Windows.Forms;
using System.Security;




namespace COMCardmanager
{

    /// <summary>
    /// call CARD_Initialize method from SDK (cardmanager.dll)
    /// </summary>
    /// <param name="param">XML filename containing settings</param>
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void CARD_initialize(String param);
    /// <summary>
    /// call card_read_data from SDK (cardmanager.dll)
    /// </summary>
    /// <param name="cardType">PC or CG</param>
    /// <param name="action">should be READ</param>
    /// <returns>data as xml format</returns>
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate bool CARD_read_data(String cardType, String action);
    /// <summary>
    /// new method to read public data (without writing local file)
    /// </summary>
    /// <param name="cardType">PC or CG</param>
    /// <param name="data">should be READ</param>
    /// <returns>data as XML format</returns>
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate int read_public_data2(String cardType, StringBuilder data);
    /// <summary>
    /// external CARD_init_reader_cycle (from SDK); initialize a cycle of card read
    /// </summary>
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void CARD_init_reader_cycle();
    /// <summary>
    /// reset cycle of card read from SDK (external call)
    /// </summary>
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void CARD_reset_reader_cycle();
    /// <summary>
    /// external call (from SDK) to terminate read operation
    /// </summary>
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void CARD_terminate();

    /// <summary>
    /// public class that handle and provide methods to read a PC and CG
    /// </summary>
    [ComVisible(true)]
    [Guid("3B835D63-ABFE-413a-9F3B-20329A2CDEEE")]
    [ComSourceInterfaces(typeof(ICard))] // Interface to use 
    [ClassInterface(ClassInterfaceType.None)]
    public class Card : COMCardmanager.ICard, IDisposable
    {
        #region constants

        private const int MAXLEN = 10000; //max 10 Ko used by chip

        #endregion

        private String PARAM = Properties.Resources.DEFAULTPARAM;
        /// <summary>
        /// parameter that allow to set and get value of full pathname of configuration xml file to use by local SDK
        /// </summary>
        public String XMLSettingsToLoad
        {
            get { return PARAM; }
            set { PARAM = value; }
        }


        /// <summary>
        /// define xml file that cardmanager should load and use
        /// </summary>
        /// <param name="szFilename"></param>
        public void setConfigToLoad(String szFilename)
        {


            if (File.Exists(szFilename))
            {
                XMLSettingsToLoad = "-x " + szFilename + " -a CARDApp -i CARDInstance";
            }
            else
            {
                string message = szFilename + " does not exist on your computer; please check your settings and call your administrator.";
                MessageBoxButtons buttons = MessageBoxButtons.OK;
                DialogResult result;

                // Displays the MessageBox.
                result = MessageBox.Show(message, "Configuration file not found on local computer", buttons);

            }




        }

        private String info = "";
        private bool disposed = false;
        private IntPtr handle = IntPtr.Zero;

        /// <summary>
        /// unmanaged CARDManager interface
        /// </summary>
        private UnmanagedLibrary lib;
        private CARD_initialize _CARD_initialize;
        private read_public_data2 _read_public_data2;
        private CARD_init_reader_cycle _CARD_init_reader_cycle;
        private CARD_reset_reader_cycle _CARD_reset_reader_cycle;
        private CARD_terminate _CARD_terminate;
        private bool isInitialize = false;
        private string instDir = @"c:\Facil\SDK\Lib";

        /// <summary>
        /// destructor
        /// </summary>

        ~Card()
        {
           //MessageBox.Show("dtor");
            // Do not re-create Dispose clean-up code here.
            // Calling Dispose(false) is optimal in terms of
            // readability and maintainability.
            Dispose(false);
        }


        // Use interop to call the method necessary
        // to clean up the unmanaged resource.
        [System.Runtime.InteropServices.DllImport("Kernel32")]
        private extern static Boolean CloseHandle(IntPtr handle);

        /// <summary>
        /// Dispose
        /// </summary>
        public void Dispose()
        {
            
            Dispose(true);
            // This object will be cleaned up by the Dispose method.
            // Therefore, you should call GC.SupressFinalize to
            // take this object off the finalization queue
            // and prevent finalization code for this object
            // from executing a second time.
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Dispose SDK
        /// </summary>
        public void DisposeSDK()
        {
            if (null != lib)
            {
                _CARD_terminate();
                lib.Dispose();
                lib = null;
            }
            isInitialize = false;
        }
        
        /// <summary>
        /// Dispose(bool disposing) executes in two distinct scenarios.
        /// If disposing equals true, the method has been called directly
        /// or indirectly by a user's code. Managed and unmanaged resources
        /// can be disposed.
        /// If disposing equals false, the method has been called by the
        /// runtime from inside the finalizer and you should not reference
        /// other objects. Only unmanaged resources can be disposed.
        /// </summary>
        /// <param name="disposing"></param>
        protected virtual void Dispose(bool disposing)
        {
            //MessageBox.Show("virtual Dispose" + disposing + disposed);
            // Check to see if Dispose has already been called.          
            if (!this.disposed)
            {
                // If disposing equals true, dispose all managed
                // and unmanaged resources.
                if (disposing)
                {                   
                    // Dispose managed resources.                    
                    DisposeSDK();
                }


                if (!IntPtr.Zero.Equals(handle))
                {
                    CloseHandle(handle);
                    handle = IntPtr.Zero;
                }

                // Note disposing has been done.
                disposed = true;

            }

        }



        /// <summary>
        /// ctor 
        /// </summary>
        /// <param name="handle"></param>

        public Card(IntPtr handle)
        {
            this.handle = handle;
        }

        /// <summary>
        /// load library cardmanager and initialize environment variables
        /// </summary>
        private void initialize()
        {
            if (isInitialize)
            {
                return;
            }

            try
            {
                #region loadlibrary
                lib = new UnmanagedLibrary(Properties.Resources.CARDMANAGER);
                if (lib == null)
                {
                    throw new Exception(Properties.Resources.UNABLETOLOADLIBRARY);
                }

                _CARD_initialize = lib.GetUnmanagedFunction<CARD_initialize>("CARD_initialize");
                _read_public_data2 = lib.GetUnmanagedFunction<read_public_data2>("read_public_data2");
                _CARD_init_reader_cycle = lib.GetUnmanagedFunction<CARD_init_reader_cycle>("CARD_init_reader_cycle");
                _CARD_reset_reader_cycle = lib.GetUnmanagedFunction<CARD_reset_reader_cycle>("CARD_reset_reader_cycle");
                _CARD_terminate = lib.GetUnmanagedFunction<CARD_terminate>("CARD_terminate");
                #endregion //loadlibrary


                instDir = GetEnvironmentVariable(instDir);

                setConfigToLoad(instDir + @"\ConfigChipMgrCardManager.xml");

                isInitialize = true;

                 _CARD_initialize(PARAM);
                 
            }
            catch (Exception e)
            {
                
                isInitialize = false;
            }

        }
       

        /// <summary>
        /// default constructor
        /// </summary>               
        public Card()
        {

        }
        /// <summary>
        /// read automobile registration chip
        /// </summary>
        /// <returns>data in json format</returns>
        public String readCG(bool isDumped = false)
        {
            try
            {
                return readCard(Properties.Resources.CG, isDumped);
            }
            catch (Exception e)
            {
                return e.Message;
            }
        }

        /// <summary>
        /// read a driver license chip
        /// </summary>
        /// <returns>data read from chip in json format</returns>
        public String readPC(bool isDumped = false)
        {
            try
            {
                return readCard(Properties.Resources.PC, isDumped);
            }
            catch (Exception e)
            {
                return e.Message;
            }
        }


        /// <summary>
        /// read a card of type cardType
        /// </summary>
        /// <param name="cardType">PC or CG</param>
        /// <param name="isDumped">true if you want to dump data read, false else</param>
        /// <returns>data read or error string</returns>
        private String readCard(String cardType, bool isDumped = false)
        {

            #region set environment variables and initialize
            StringBuilder data = new StringBuilder(MAXLEN);
            data.Clear();
            String szData = "";
            bool isRead = false;
            String result = "";


            try
            {
                initialize();
            }
            catch (Exception e)
            {
                return e.Message;
            }

            if (!isInitialize)
            {
                return Properties.Resources.UNABLETOINITIALIZECOM;
            }

            #endregion

            #region load library call methods to read card
            try
            {
                // using (UnmanagedLibrary lib = new UnmanagedLibrary(Properties.Resources.CARDMANAGER))
                {



                    /*
                    _CARD_initialize(PARAM);
                   
                     */                     
                      _CARD_init_reader_cycle();
                    #region read data
                    try
                    {
                        int len = _read_public_data2(cardType, data);
                        if (len > 0)
                        {
                            szData = data.ToString(0, len - 1);
                            info += " read successfull..." + cardType;
                            isRead = true;
                        }
                        else
                        {
                            //Console.WriteLine("ERROR: unable to read card...");
                            info += Properties.Resources.UNABLETOREADDATA;
                            isRead = true;
                        }
                        data.Clear();
                        data = null;
                    }
                    catch (Exception e)
                    {
                        szData = e.Message;
                        isRead = false;
                    }

                    #endregion


                    _CARD_reset_reader_cycle();
                    /*
                     * _CARD_terminate();
                     * */

                }//normally, library is unload at this step automatically
            #endregion

                #region prepare_data

                if (!isRead)
                {
                    return Properties.Resources.UNABLETOREADDATA;
                }

                try
                {
                    //try to convert xml in JSON format                    
                    XmlDocument doc = new XmlDocument();
                    Encoding enc = Encoding.Default;
                    string asciiString = convertTo(szData, enc);
                    MemoryStream stream = new MemoryStream(enc.GetBytes(asciiString));
                    stream.Flush();
                    stream.Position = 0;
                    doc.XmlResolver = null;
                    doc.Load(stream);
                    //doc.LoadXml(Encoding.UTF8.GetString(convertToUTF8(szData)));
                    result = JsonConvert.SerializeXmlNode(doc, Newtonsoft.Json.Formatting.Indented);

                    //dump(isDumped, szData, doc);
                    stream = null;
                    doc = null;
                    enc = null;
                }
                catch (Exception e)
                {

                    result = Properties.Resources.UNABLETOREADDATA;
                }

                #endregion

                return result;
            }
            catch (Exception e)
            {
                //MessageBox.Show(e.Message + e.StackTrace);
                return e.Message;
            }
            finally
            {

/*
                if (null != lib)
                {
                    lib.Dispose();
                    lib = null;
                }
                isInitialize = false;
 */
            }

        }


        /// <summary>
        /// try to read environment variable
        /// set currentdir to instDir (read in environment variables or passed 
        /// as default parameter in exception case
        /// </summary>
        /// <param name="instDir">default value to use</param>
        /// <returns>value read or default value if exception</returns>
        private String GetEnvironmentVariable(String instDir)
        {
            //try to get environment variable
            try
            {
                instDir = Environment.GetEnvironmentVariable(Properties.Resources.INSTDIR);

            }
            catch (ArgumentNullException ane)
            {
                //variable is not set
                //use default value in this case

            }
            catch (SecurityException se)
            {
                //user does not have right to read environment variable
                //use default value in this case
            }
            finally
            {
                Environment.CurrentDirectory = instDir;
            }
            return instDir;
        }
        /// <summary>
        /// try to dump data in file
        /// </summary>
        /// <param name="isDumped"></param>
        /// <param name="szData"></param>
        /// <param name="doc"></param>
        private void dump(bool isDumped, string szData, XmlDocument doc)
        {
            if (isDumped)
            {
                Encoding ascii = Encoding.Default;
                Encoding unicode = Encoding.Unicode;
                Encoding utf8 = Encoding.UTF8;
                Encoding w1256 = Encoding.GetEncoding(1256);

                string asciiString = convertTo(szData, ascii);
                string utf8String = convertTo(szData, utf8);
                string w1256String = convertTo(szData, w1256);

                TextWriter tw = new StreamWriter(@"dumpASCII.xml", true, ascii);
                tw.Write(asciiString);
                tw.Close();

                tw = new StreamWriter(@"dumpUTF8.xml", true, utf8);
                tw.Write(utf8String);
                tw.Close();

                tw = new StreamWriter(@"dump1256.xml", true, w1256);
                tw.Write(w1256String);
                tw.Close();

                tw = new StreamWriter(@"dumpUnicode.xml", true, unicode);
                tw.Write(szData);
                tw.Close();
            }
        }


        /// <summary>
        /// convert a unicode string to enc format 
        /// </summary>
        /// <param name="szData">unicode string</param>
        /// <param name="enc">define destination encoding to use</param>
        /// <returns>encoding string</returns>
        private String convertTo(string szData, Encoding enc)
        {
            Encoding ascii = enc;
            Encoding unicode = Encoding.Unicode;

            byte[] unicodeBytes = unicode.GetBytes(szData);

            // Perform the conversion from one encoding to the other.
            byte[] asciiBytes = Encoding.Convert(unicode, ascii, unicodeBytes);

            char[] asciiChars = new char[ascii.GetCharCount(asciiBytes, 0, asciiBytes.Length)];

            ascii.GetChars(asciiBytes, 0, asciiBytes.Length, asciiChars, 0);
            string asciiString = new string(asciiChars);

            return asciiString;
        }

    }
}
